package cn.foolishbird.crow.core.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.chrono.ChronoLocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import cn.foolishbird.crow.core.cache.WeakCache;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author foolishbird
 * @version 1.0
 */
public final class DateTimeUtils {

    private static Logger logger = LoggerFactory.getLogger(DateTimeUtils.class);

    /**
     * DateTimeFormatter 格式化缓存
     */
    private static final WeakCache<DateTimeEnum, DateTimeFormatter> FORMATTER_CACHE = new WeakCache<>();

    /**
     * 时间格式化类
     */
    private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT_THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 时间字符串转换成date
     *
     * @param source
     * @param format
     * @return
     */
    public static Date parseDate(String source, DateTimeEnum format) {
        SimpleDateFormat sdf = DATE_FORMAT_THREAD_LOCAL.get();
        if (Objects.isNull(sdf)) {
            sdf = new SimpleDateFormat(format.getFormat());
            DATE_FORMAT_THREAD_LOCAL.set(sdf);
        }
        try {
            return sdf.parse(source);
        } catch (ParseException e) {
            logger.error("时间格式化失败:{}", e);
            throw new RuntimeException("时间格式化失败");
        }
    }

    /**
     * The time string is converted to {DateTimeEnum}
     *
     * @param timeStr time string
     * @param format  time format
     * @return
     */
    public static LocalDate parseLocalDate(String timeStr, DateTimeEnum format) {
        if (StringUtils.isBlank(timeStr)) {
            throw new IllegalArgumentException("timeStr is not null");
        }
        if (Objects.isNull(format)) {
            throw new IllegalArgumentException("format is not null");
        }
        return LocalDate.parse(timeStr, dateTimeFormatter(format));
    }

    /**
     * 时间字符串转换成LocalDateTime
     *
     * @param timeStr
     * @param format
     * @return
     */
    public static LocalDateTime parseLocalDateTime(String timeStr, DateTimeEnum format) {
        if (StringUtils.isBlank(timeStr)) {
            throw new IllegalArgumentException("timeStr is not null");
        }
        if (Objects.isNull(format)) {
            throw new IllegalArgumentException("format is not null");
        }
        return LocalDateTime.parse(timeStr, dateTimeFormatter(format));
    }

    /**
     * TemporalAccessor is converted to time string
     *
     * @param temporalAccessor TemporalAccessor
     * @param format           time string format
     * @return
     */
    public static String format(TemporalAccessor temporalAccessor, DateTimeEnum format) {
        if (Objects.isNull(temporalAccessor)) {
            throw new IllegalArgumentException("temporalAccessor is not null");
        }
        if (Objects.isNull(format)) {
            throw new IllegalArgumentException("format is not null");
        }
        return dateTimeFormatter(format).format(temporalAccessor);
    }

    /**
     * 时间转换到凌晨
     *
     * @param date
     * @return
     */
    public static Date toWeeHours(Date date) {
        if (Objects.isNull(date)) {
            return null;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.MILLISECOND, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        return calendar.getTime();
    }

    /**
     * 时间转换到凌晨
     * @param calendar
     */
    public static void toWeeHours(Calendar calendar) {
        if (Objects.isNull(calendar)) {
            throw new IllegalArgumentException("时间不能为空");
        }
        calendar.set(Calendar.MILLISECOND, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
    }

    /**
     * 判断是否是同一天
     *
     * @param prefixDate
     * @param suffixDate
     * @return
     */
    public static boolean sameDay(ChronoLocalDateTime<?> prefixDate, ChronoLocalDateTime<?> suffixDate) {
        if (Objects.isNull(prefixDate) || Objects.isNull(suffixDate)) {
            throw new IllegalArgumentException("时间不能为空");
        }
        return prefixDate.toLocalDate().toEpochDay() == suffixDate.toLocalDate().toEpochDay();
    }

    /**
     * 是否相同一天
     *
     * @param prefixDate
     * @param suffixDate
     * @return
     */
    public static boolean sameDay(Calendar prefixDate, Calendar suffixDate) {
        return difference(prefixDate, suffixDate, TimeUnit.DAYS) == 0;
    }

    /**
     * 是否相同一天
     *
     * @param prefixDate
     * @param suffixDate
     * @return
     */
    public static boolean sameDay(Date prefixDate, Date suffixDate) {
        return difference(prefixDate, suffixDate, TimeUnit.DAYS) == 0;
    }

    /**
     * 获取时间间隔
     *
     * @param prefixDate
     * @param suffixDate
     * @param timeUnit
     * @return
     */
    public static long difference(LocalDateTime prefixDate, LocalDateTime suffixDate, ZoneOffset offset, TimeUnit timeUnit) {
        if (Objects.isNull(prefixDate) || Objects.isNull(suffixDate)) {
            throw new IllegalArgumentException("间隔时间不能为空");
        }
        return difference(prefixDate, suffixDate, t -> t.toInstant(offset).toEpochMilli(),
                t -> timeUnit.convert(t, TimeUnit.MILLISECONDS));
    }

    /**
     * 获取时间间隔
     *
     * @param prefixDate
     * @param suffixDate
     * @param timeUnit
     * @return
     */
    public static long difference(Calendar prefixDate, Calendar suffixDate, TimeUnit timeUnit) {
        if (Objects.isNull(prefixDate) || Objects.isNull(suffixDate)) {
            throw new IllegalArgumentException("间隔时间不能为空");
        }
        return difference(prefixDate, suffixDate, Calendar::getTimeInMillis,
                t -> timeUnit.convert(t, TimeUnit.MILLISECONDS));
    }

    /**
     * 获取时间间隔
     *
     * @param prefixDate
     * @param suffixDate
     * @param timeUnit
     * @return
     */
    public static long difference(Date prefixDate, Date suffixDate, TimeUnit timeUnit) {
        if (Objects.isNull(prefixDate) || Objects.isNull(suffixDate)) {
            throw new IllegalArgumentException("间隔时间不能为空");
        }
        return difference(prefixDate, suffixDate, Date::getTime,
                t -> timeUnit.convert(t, TimeUnit.MILLISECONDS));
    }

    /**
     * 相差时间数
     *
     * @param prefixDate
     * @param suffixDate
     * @param resultApply
     * @param <R>
     * @return
     */
    private static <T, R> R difference(T prefixDate, T suffixDate,
                                       Function<T, Long> parameterApply, Function<Long, R> resultApply) {
        long difference = Math.abs(parameterApply.apply(prefixDate) - parameterApply.apply(suffixDate));
        return resultApply.apply(difference);
    }

    /**
     * 获取DateTimeFormatter
     *
     * @param formatEnum
     * @return
     */
    private static DateTimeFormatter dateTimeFormatter(DateTimeEnum formatEnum) {
        DateTimeFormatter formatter = FORMATTER_CACHE.get(formatEnum);
        if (Objects.isNull(formatter)) {
            synchronized (FORMATTER_CACHE) {
                formatter = FORMATTER_CACHE.get(formatEnum);
                if (Objects.isNull(formatter)) {
                    formatter = DateTimeFormatter.ofPattern(formatEnum.getFormat());
                    FORMATTER_CACHE.put(formatEnum, formatter);
                }
            }
        }
        return formatter;
    }

}
